package com.csoft.riskroll;

import com.commonsware.android.sensor.Shaker;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.*;

/**
 * RiskRoll v 1.0
 * @author John Cline
 * 13 July 2010
 *
 * Note: Version number is relatively arbitrary. This isn't my 1.0 release, but it's
 * better than a 0.1.
 * 
 * Anyways, here's the code. I use the "Shaker" class provided by commonsware
 * (available at http://github.com/commonsguy/cw-advandroid ) to provide the
 * "shake to roll" feature. This simple application simulates the dice rolling
 * for the Risk board game. It has selection buttons for the number of die for
 * each side at the top, and displays a simple scrollable output window.
 * 
 * Users can roll and clear with buttons, or roll by shaking the phone. Phone
 * also enables vibrate for "haptic" feedback after rolling by shaking. The
 * "Nuclear" option is also able to be set in the preferences, when the attack
 * rolls three of a kind.
 * 
 * Dice icon obtained from http://www.psdgraphics.com/
 * This code is distributed under Apache v2, available http://www.apache.org/licenses/LICENSE-2.0
 * Please note that "Shaker" is licensed under Apache v2.
 */

public class RiskRoll extends Activity implements Shaker.Callback {
    /* 
     * I feel the variables are relatively self-explanatory.
     * Arrays to represent the die/buttons, integers to keep track of
     * what's going on, and a few others for various features (such
     * as the shaker and toast notification window).
     */
	private Button[] attackButtons;
    private Button[] defendButtons;
    private TextView outputBox;
    private ScrollView outputScroll;
    private Button rollBut;
    private Button clearBut;
    private String eol;
    private boolean nukesOn;
    private boolean vibeOn;
    private boolean shakeOn;
    private SharedPreferences settings;
    private Toast toastWin;
    private Shaker shaker;
    private Vibrator vibrator;
    private RiskDie dieSet;
    private int[] lossCount;
    private int[] resultDie;
    
    private static final String ERROR_ROLL = "Invalid roll";
    private static final String SAVED_OUTPUT = "";
    private static final String ATTACK_BUTTON_STATE = "A";
    private static final String DEFEND_BUTTON_STATE = "D";
    private static final int VIBE_LENGTH = 200;
    private static final double THRESHOLD = 1.1d;
	
	/** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        /*
         * Setups up the initial view elements and 
         * calls the variable initiation function.
         */
        setContentView(R.layout.roll);
        attackButtons = new Button[3];
        attackButtons[0] = (Button) findViewById(R.id.attackBut1);
        attackButtons[1] = (Button) findViewById(R.id.attackBut2);
        attackButtons[2] = (Button) findViewById(R.id.attackBut3);
        attackButtons[1].setEnabled(false);
        defendButtons = new Button[2];
        defendButtons[0] = (Button) findViewById(R.id.defendBut1);
        defendButtons[1] = (Button) findViewById(R.id.defendBut2);
        defendButtons[0].setEnabled(false);
        outputBox = (TextView) findViewById(R.id.outputBox);
        rollBut = (Button) findViewById(R.id.rollBut);
        clearBut = (Button) findViewById(R.id.clearBut);
        outputScroll = (ScrollView) findViewById(R.id.outputScroll);
        initVar();
    }
    
    private void initVar() {
    	/*
    	 * Initializes all non-view elements.
    	 * Sets the attack die count to the default 2 (as listed
    	 * in the roll.xml layout) and defend as 1 (listed in the same place).
    	 * Sets up listeners for the buttons, and defaults everything.
    	 */
    	dieSet = new RiskDie(getAttackCount(), getDefendCount());
        for (int i=0;i<attackButtons.length;i++) {
        	attackButtons[i].setOnClickListener(new OnClickListener () {
        		public void onClick(View v) {
        			changeAttackCount(v.getId());
        		}
        	});
        }
        for (int i=0;i<defendButtons.length;i++) {
        	defendButtons[i].setOnClickListener(new OnClickListener () {
        		public void onClick(View v) {
        			changeDefendCount(v.getId());
        		}
        	});
        }
        rollBut.setOnClickListener(new OnClickListener () {
        	public void onClick(View v) {
        		rollDie();
        	}
        });
        clearBut.setOnClickListener(new OnClickListener () {
        	public void onClick(View v) {
        		clearOutput();
        	}
        });
        eol = new String(System.getProperty("line.separator"));
    }
 
    private int getAttackCount() {
    	/*
    	 * Returns the current number of attack die selected.
    	 */
		for (int i=0;i<attackButtons.length;i++) {
			if (!attackButtons[i].isEnabled()) return (i+1);
		}
		return 0;
	}
    
    private int getDefendCount() {
    	/*
    	 * Returns the current number of defend die selected.
    	 */
    	for (int i=0;i<defendButtons.length;i++) {
			if (!defendButtons[i].isEnabled()) return (i+1);
		}
		return 0;
    }
    
    private void changeAttackCount(int id) {
    	/*
    	 * Runs through the buttons to see which was selected,
    	 * and dis/enables the others accordingly.
    	 */
    	if (id == R.id.attackBut1) {
    		attackButtons[0].setEnabled(false);
    		attackButtons[1].setEnabled(true);
    		attackButtons[2].setEnabled(true);
    		dieSet.setNumDie(1, -1);
    	} else if (id == R.id.attackBut2) {
    		attackButtons[0].setEnabled(true);
    		attackButtons[1].setEnabled(false);
    		attackButtons[2].setEnabled(true);
    		dieSet.setNumDie(2, -1);
    	} else {
    		attackButtons[0].setEnabled(true);
    		attackButtons[1].setEnabled(true);
    		attackButtons[2].setEnabled(false);
    		dieSet.setNumDie(3, -1);
    	}
    }
    
    private void changeDefendCount(int id) {
    	/*
    	 * Runs through the buttons to see which was selected,
    	 * and dis/enables the others accordingly.
    	 */
    	if (id == R.id.defendBut1) {
    		defendButtons[0].setEnabled(false);
    		defendButtons[1].setEnabled(true);
    		dieSet.setNumDie(-1, 1);
    	} else {
    		defendButtons[0].setEnabled(true);
    		defendButtons[1].setEnabled(false);
    		dieSet.setNumDie(-1, 2);
    	}
    }
    
    private void rollDie() {
    	/*
    	 * Rolls the die, corresponding to the number selected by the user.
    	 * First checks to see if more defense die are rolled than attack,
    	 * which is not allowed by Risk rules. Displays toast popup if this is
    	 * the case.
    	 * Otherwise, rolls each die selected and sorts highest to lowest for display.
    	 * Calls displayResults, and resets the die to 0.
    	 */
    	lossCount = dieSet.rollDie();
    	if ((lossCount[0] == -1) && (lossCount[1] == -1)) {
    		displayNotification(ERROR_ROLL);
    	} else {
    		displayResults();
    	}
    }
    
    private void clearOutput() {
    	/*
    	 * Clears output text window.
    	 */
    	outputBox.setText("");
    }
    
    private void displayResults() {
    	/*
    	 * Displays results of the roll. Builds a string as it goes along 
    	 * calculating the results.
    	 * First displays the actual roll-out, then displays how many
    	 * each side loses (roll-out and losses are separated by a "||"
    	 * Additionally checks if nukes are enabled, and displays if a nuke
    	 * is rolled.
    	 * Sets the scroll window to the most recent result.
    	 */
    	resultDie = dieSet.getAttackDie();
    	String results = new String();
    	results = "A: ";
    	for (int i=0;i<dieSet.getAttackCount();i++) {
    		results += Integer.toString(resultDie[i]) + " ";
    	}
    	resultDie = dieSet.getDefendDie();
    	results = results + "D: ";
    	for (int i=0;i<dieSet.getDefendCount();i++) {
    		results += Integer.toString(resultDie[i]) + " ";
    	}
    	results += " || ";
    	if (lossCount[0] == 0) {
    		results += "Defense lose " + Integer.toString(lossCount[1]) + " ";
    	} else {
    		results += "Attack lose " + Integer.toString(lossCount[0]) + " ";
    		if (lossCount[1] != 0) {
        		results += "Defense lose " + Integer.toString(lossCount[1]) + " ";
        	}
    	}
    	outputBox.setText(outputBox.getText()+results+eol);
    	if (nukesOn && dieSet.getNuke()) {
    		outputBox.setText(outputBox.getText()+getString(R.string.nuke_display)+eol);
    	}
    	outputScroll.fullScroll(ScrollView.FOCUS_DOWN);
    }

    /*
     * This function was replaced by displayNotifaction, but
     * left in for future debugging use.
	private void displayResults(String msg) {
    	outputBox.setText(outputBox.getText()+msg+eol);
    }
    */
	
	private void displayNotification(String msg) {
		/*
		 * Displays a toast popup with the text contained in msg.
		 */
		toastWin = Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT);
		toastWin.show();
	}
    
    @Override  
    protected void onRestoreInstanceState(Bundle savedInstanceState) {  
    	super.onRestoreInstanceState(savedInstanceState);
    	/*
    	 * Restores settings on return to the application, specifically
    	 * the number of die and any output text left in the window.
    	 */
    	outputBox.setText(savedInstanceState.getString(SAVED_OUTPUT));
    	dieSet.setNumDie(savedInstanceState.getInt(ATTACK_BUTTON_STATE), 
    			savedInstanceState.getInt(DEFEND_BUTTON_STATE));
        restoreDie();
    }  

	@Override  
    protected void onSaveInstanceState(Bundle outState) {   
		super.onSaveInstanceState(outState); 
		/*
    	 * Saves settings on exit of the application, specifically
    	 * the number of die and any output text left in the window.
    	 */
		outState.putString(SAVED_OUTPUT, (String) outputBox.getText());  
        outState.putInt(ATTACK_BUTTON_STATE, dieSet.getAttackCount());
        outState.putInt(DEFEND_BUTTON_STATE, dieSet.getDefendCount());
    } 
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
    	/*
    	 * Creates the options menu from the options.xml file.
    	 */
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.options_menu, menu);
        return true;
    }
    
    @Override
    public void onStart() {
    	super.onStart();
    	/*
    	 * Loads preferences on startup.
    	 */
    	getPrefs();
    }
    
    @Override 
    public void onResume() {
    	super.onResume();
    	/* 
    	 * Sets up the shake and vibrate features if those options are selected
    	 * in the preferences window.
    	 */
    	if (shakeOn) shaker = new Shaker(this, THRESHOLD, 500, this);
        if (vibeOn) vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
    }
    
    @Override
    public void onPause() {
    	super.onPause();
    	/*
    	 * Disables the shaker and vibrators while the Roll application
    	 * is not in the foreground.
    	 */
    	if (shaker != null) shaker.close();
    	shaker = null;
    	vibrator = null;
    }
    
    @Override
    public void onStop() {
    	super.onStop();
    }
    
    public boolean onOptionsItemSelected(MenuItem item) {
    	/*
    	 * Figures out which option was selected, and loads accordingly.
    	 */
    	switch (item.getItemId()) {
    		case R.id.switch_screen:
    			startActivity(new Intent(this, RiskAutoRoll.class));
    			this.finish();
    			return true;
	        case R.id.options:
	            startActivity(new Intent(this, Preferences.class));
	            return true;
	        case R.id.quit:
	            this.finish();
	            return true;
        }
        return false;
    }

    private void getPrefs() {
    	/*
    	 * Loads the preferences into locally stored variables.
    	 */
    	settings = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
    	nukesOn = settings.getBoolean("nukes_enabled", false);
    	vibeOn = settings.getBoolean("vibrate_enabled", false);
    	shakeOn = settings.getBoolean("shake_enabled", false);
    }
    
    private void restoreDie() {
    	/*
    	 * Restores the current button selections after a save
    	 * based on the attack and defend die counts.
    	 */
		if (dieSet.getAttackCount() == 1) {
			attackButtons[0].setEnabled(false);
			attackButtons[1].setEnabled(true);
			attackButtons[2].setEnabled(true);
		} else if (dieSet.getAttackCount() == 2) {
			attackButtons[0].setEnabled(true);
			attackButtons[1].setEnabled(false);
			attackButtons[2].setEnabled(true);
		} else {
			attackButtons[0].setEnabled(true);
			attackButtons[1].setEnabled(true);
			attackButtons[2].setEnabled(false);
		}
		if (dieSet.getDefendCount() == 1) {
			defendButtons[0].setEnabled(false);
			defendButtons[1].setEnabled(true);
		} else {
			defendButtons[0].setEnabled(true);
			defendButtons[1].setEnabled(false);
		}
	}
    
    @Override
	public void onDestroy() {
		super.onDestroy();		
	}
	
	public void shakingStarted() {
		/*
		 * Implemented for Shaker class. When shaking is started, roll
		 * the die and vibrate afterwards.
		 */
		rollDie();
        if (vibrator != null) vibrator.vibrate(VIBE_LENGTH);
	}
	
	public void shakingStopped() {
		return;
	}
}